#include "dccfxplayer.h"

#define combine8to16(high8, low8) ((unsigned int)(high8) << 8 | (unsigned int)(low8))

/* Time tick processing, placed in a 1ms timer */
void dccfxplayer_time_tick (struct dccfxplayer *pl) {
    static uint16_t count_uint = 0;

    if (pl->frame_time_unit == TIMEUNIT_K_MS) {
        pl->frame_curr_time++;
    } else if (pl->frame_time_unit == TIMEUNIT_K_S) {
        if (++count_uint >= 1000) {
            pl->frame_curr_time++;
            count_uint = 0;
        }
    } else if (pl->frame_time_unit == TIMEUNIT_K_MIN) {
        if (++count_uint >= 60000) {
            pl->frame_curr_time++;
            count_uint = 0;
        }
    }
}

/* Read partition operation parameters */
static void fxplayer_cb_read_zone_params (struct dccfxplayer *pl) {

    if (pl->p_read_buffer == NULL) {
        DCCFX_ERROR (" p_read_buffer is NULL\n");
        goto err;
    }

    if (pl->cb_read_zone == NULL) {
        DCCFX_ERROR ("The read zone function has not been implemented, stop\r\n");
        goto err;
    }

    if (pl->cb_turn_off == NULL) {
        DCCFX_ERROR ("The cb_turn_off function has not been implemented, stop\r\n");
        goto err;
    }

    if (pl->cb_read_zone (pl)) {
        DCCFX_ERROR ("cb_read_zone fail!, stop\r\n");
        goto err;
    }

    pl->channel_count = combine8to16 (pl->p_read_buffer[0], pl->p_read_buffer[1]);
    pl->frame_count = combine8to16 (pl->p_read_buffer[2], pl->p_read_buffer[3]);

    for (int i = 0; i < 5; i++) {
        pl->preset_time_values[i] = combine8to16 (pl->p_read_buffer[(i + 1) * 3 + 1], pl->p_read_buffer[(i + 1) * 3 + 2]);
        pl->preset_time_units[i] = pl->p_read_buffer[(i + 1) * 3 + 3];
    }
    pl->enabled = pl->p_read_buffer[19];
    pl->loop_type = pl->p_read_buffer[20];
    pl->loop_count_max = (uint16_t)pl->p_read_buffer[21] | pl->p_read_buffer[22];
    pl->loop_end_action = pl->p_read_buffer[23];

    pl->led_count = pl->channel_count / 3;
    pl->frame_size = pl->channel_count + 3;

    if ((pl->enabled == 0) ||
        (pl->enabled == 0xff) ||
        (pl->frame_count == 0x00)) {
        DCCFX_ERROR ("zone data error, enable:%d, frames:%d\r\n",
                     pl->enabled, pl->frame_count);
        goto err;
    }

    pl->curr_frame = 0;
    pl->frame_curr_time = 0;
    pl->curr_state = RUNSTATE_K_READ_FRAME;
    goto _exit;

err:
    DCCFX_ERROR ("read zone param fail!, stop and exit\r\n");
    pl->curr_state = RUNSTATE_K_ERR;
_exit:

#if DEBUG_LEVEL >= 2
    DCCFX_PRINT ("ZONE%d, %08X:\n", pl->curr_zone, pl->read_addr_curr);
    DCCFX_PRINT ("  LED Count = %d, Frame Count = %d\n", pl->led_count, pl->frame_count);
    DCCFX_PRINT ("  Loop Type = %d, Loop Count Max = %d, Loop End Action = %d\n",
                 pl->loop_type, pl->loop_count_max, pl->loop_end_action);
    for (int i = 0; i < 5; i++) {
        DCCFX_PRINT ("  Preset%d: %4d, %d\n",
                     i + 1, pl->preset_time_values[i], pl->preset_time_units[i]);
    }
#endif

    return;
}

uint32_t scale_frame_time (uint32_t frame_time, uint8_t play_speed) {

#define DEFAULT_SPEED 5   // 默认速度档位
#define MIN_FRAME_TIME 3  // 最小帧时间

    const uint16_t speed_percentage_lookup[] = {
        200, 180, 160, 140, 120, 100, 80, 60, 40, 20, 5};

    if (play_speed > 10) {
        return frame_time;
    }

    if (play_speed == DEFAULT_SPEED) {
        return frame_time;
    }

    uint16_t percentage = speed_percentage_lookup[play_speed];

    uint32_t new_time = (frame_time * percentage) / 100;

    if (new_time < MIN_FRAME_TIME) {
        new_time = MIN_FRAME_TIME;
    }

    // printf("scale_frame_time:%d\r\n", new_time);
    return new_time;
}

/* Read frame data */
static void fxplayer_read_frame_data (struct dccfxplayer *pl) {

    uint16_t temp_time;

    if (pl->cb_read_frame == NULL) {
        DCCFX_ERROR ("The read frame function has not been implemented, stop\r\n");
        goto err;
    }

    if (pl->cb_read_frame (pl) != 0) {
        DCCFX_ERROR ("read_frame fail!, stop\r\n");
        goto err;
    }

    pl->frame_time_type = pl->p_read_buffer[2];
    temp_time = combine8to16 (pl->p_read_buffer[0], pl->p_read_buffer[1]);

    if (pl->frame_time_type == 0) {
        pl->frame_time_target = pl->preset_time_values[temp_time - 1];
        pl->frame_time_unit = pl->preset_time_units[temp_time - 1];
    } else {
        pl->frame_time_target = temp_time;
        pl->frame_time_unit = pl->frame_time_type - 1;
    }

    if (((pl->frame_time_unit != TIMEUNIT_K_MS) &&
         (pl->frame_time_unit != TIMEUNIT_K_S) &&
         (pl->frame_time_unit != TIMEUNIT_K_MIN)) ||
        (pl->frame_time_target > 1000)) {
        DCCFX_ERROR ("time set error!\r\n");
        goto err;
    }

    pl->frame_time_target = scale_frame_time (pl->frame_time_target, pl->play_speed);
    pl->curr_state = RUNSTATE_K_SEND_FRAME;
    goto _exit;

err:
    DCCFX_ERROR ("read frame data fail!, stop and exit\r\n");
    pl->curr_state = RUNSTATE_K_ERR;

_exit:

#if DEBUG_LEVEL >= 3
    DCCFX_PRINT ("READ FRAME: [");
    for (int i = 0; i < pl->frame_size; i++) {
        DCCFX_PRINT ("%02X ", pl->p_read_buffer[i]);
    }
    DCCFX_PRINT ("]\r\n");
#endif
    return;
}

/* Send frame data */
static void fxplayer_send_frame_data (struct dccfxplayer *pl) {

    if (pl->cb_send_frame == NULL) {
        DCCFX_ERROR ("The send frame function has not been implemented, stop\r\n");
        pl->curr_state = RUNSTATE_K_ERR;
        return;
    }

    pl->cb_send_frame (pl);
    pl->frame_curr_time = 0;
    pl->curr_state = RUNSTATE_K_WAIT_FRAME_END;

#if DEBUG_LEVEL >= 2
    DCCFX_PRINT ("SNED FRAME: %d of %d\n", pl->curr_frame + 1, pl->frame_count);
#endif
    return;
}

/* After sending the light data, wait for the current frame's time to end */
static void fxplayer_wait_frame_end (struct dccfxplayer *pl) {

    if (pl->frame_curr_time >= pl->frame_time_target) {
        pl->frame_curr_time = 0;
        if (pl->curr_frame++ < pl->frame_count - 1) {
            pl->curr_state = RUNSTATE_K_READ_FRAME;
        } else {
            pl->curr_state = RUNSTATE_K_ZONE_END;
        }
    }
}

/* Partition effect ends, jump according to actions */
static void fxplayer_zone_end (struct dccfxplayer *pl) {

    if (pl->cb_end_work == NULL) {
        DCCFX_ERROR ("The read end function has not been implemented, stop\r\n");
        pl->curr_state = RUNSTATE_K_ERR;
        return;
    }

    pl->cb_end_work();

    switch (pl->loop_type) {
    case LOOPTYPE_K_INVALID:  // 上位机bug,若无设置,默认无限循环
        pl->loop_type = LOOPTYPE_K_INFINITE;
        pl->curr_state = RUNSTATE_K_READ_ZONE_PARAS;
        break;

    case LOOPTYPE_K_INFINITE:  // 无限循环,从头开始放
        pl->curr_state = RUNSTATE_K_READ_ZONE_PARAS;
        break;

    case LOOPTYPE_K_MULTIPLE:  // 多次循环
        if (++pl->curr_loop < pl->loop_count_max) {
            pl->curr_frame = 0;
            pl->curr_state = RUNSTATE_K_READ_ZONE_PARAS;
        } else {  // 循环到目标次数
            pl->curr_loop = 0;
            switch (pl->loop_end_action) {
            case ENDACTION_K_JUMP_NEXT_ZONE:  // 跳到下一分区
                if (pl->curr_zone < 3) {
                    pl->curr_zone++;
                } else {
                    pl->curr_zone = 0;
                }
                pl->curr_state = RUNSTATE_K_READ_ZONE_PARAS;
                break;
            case ENDACTION_K_KEEP_END_FRAME:  // 保持最后一帧
                pl->curr_state = RUNSTATE_K_STOP;
                break;
            case ENDACTION_K_CLOSE_LAMP:  // 关灯
                if (pl->cb_turn_off != NULL)
                    pl->cb_turn_off();
                pl->curr_state = RUNSTATE_K_STOP;
                break;
            }
        }
        break;

    case LOOPTYPE_K_SINGLE:               // 单次循环
        switch (pl->loop_end_action) {
        case ENDACTION_K_JUMP_NEXT_ZONE:  // 跳到下一分区
            if (pl->curr_zone < 3) {
                pl->curr_zone++;
            } else {
                pl->curr_zone = 0;
            }
            pl->curr_state = RUNSTATE_K_READ_ZONE_PARAS;
            break;
        case ENDACTION_K_KEEP_END_FRAME:  // 保持最后一帧
            pl->curr_state = RUNSTATE_K_STOP;
            break;
        case ENDACTION_K_CLOSE_LAMP:  // 关灯
            if (pl->cb_turn_off != NULL)
                pl->cb_turn_off();
            pl->curr_state = RUNSTATE_K_STOP;
            break;
        }
        break;
    }
#if DEBUG_LEVEL >= 2
    DCCFX_PRINT ("ZONE END: type:%d, value:%d, act:%d\r\n", pl->loop_type, pl->loop_count_max, pl->loop_end_action);
#endif
}

void fxplayer_run_error (struct dccfxplayer *pl) {

    if (pl->cb_run_err != NULL) {
        pl->cb_run_err();
    }

    pl->curr_state = RUNSTATE_K_STOP;
}

/* fxplayer state machine polling, used in the main loop */
void dccfxplayer_state_poll (struct dccfxplayer *pl) {
    if (pl->runing == 1) {
        switch (pl->curr_state) {
        case RUNSTATE_K_READ_ZONE_PARAS:
            fxplayer_cb_read_zone_params (pl);
            break;
        case RUNSTATE_K_READ_FRAME:
            fxplayer_read_frame_data (pl);
            break;
        case RUNSTATE_K_SEND_FRAME:
            fxplayer_send_frame_data (pl);
            break;
        case RUNSTATE_K_WAIT_FRAME_END:
            fxplayer_wait_frame_end (pl);
            break;
        case RUNSTATE_K_ZONE_END:
            fxplayer_zone_end (pl);
            break;
        case RUNSTATE_K_ERR:
            fxplayer_run_error (pl);
            break;
        default:
            break;
        }
    }
}

void dccfxplayer_init (struct dccfxplayer *pl,
                       uint8_t *buffer,
                       int (*read_zone) (struct dccfxplayer *),
                       int (*read_frame) (struct dccfxplayer *),
                       void (*send_frame) (struct dccfxplayer *),
                       void (*end_work) (void),
                       void (*turn_off) (void),
                       void (*run_err) (void),
                       uint8_t speed) {
    if (pl == NULL) {
        DCCFX_ERROR ("INIT: dccfxplayer pointer is NULL\r\n");
        return;
    }

    pl->p_read_buffer = buffer;
    pl->cb_read_zone = read_zone;
    pl->cb_read_frame = read_frame;
    pl->cb_send_frame = send_frame;
    pl->cb_end_work = end_work;
    pl->cb_turn_off = turn_off;
    pl->cb_run_err = run_err;

    pl->play_speed = speed;
}

void dccfxplayer_start (struct dccfxplayer *pl, uint8_t zone) {

    if (pl == NULL) {
        DCCFX_ERROR ("Error: dccfxplayer pointer is NULL\r\n");
        return;
    }

    if (pl->cb_read_zone == NULL || pl->cb_read_frame == NULL || pl->cb_send_frame == NULL) {
        DCCFX_ERROR ("Error: required callbacks not set\r\n");
        return;
    }

    pl->runing = 1;
    pl->curr_state = RUNSTATE_K_READ_ZONE_PARAS;
    pl->curr_zone = zone;
}

void dccfxplayer_stop (struct dccfxplayer *pl) {

    if (pl == NULL) {
        DCCFX_ERROR ("Error: dccfxplayer pointer is NULL\r\n");
        return;
    }

    pl->cb_turn_off();
    pl->runing = 0;
}
